
#include <api/libfs2/stdio.h>
#include <api/libnet/libnet.h>
#include <api/libcurl/curl.h>
#include <api/libcurl/types.h>
#include <api/libcurl/easy.h>



#if 0
#define LIBNET_PRINTF	libc_printf
#else
#define LIBNET_PRINTF(...)	do{}while(0)
#endif

libnet_callback libnet_cb;

typedef enum _DownLoadStatus
{
	LIBNET_DOWNLOAD_START,
	LIBNET_DOWNLOAD_FINISH,
	LIBNET_DOWNLOAD_ABORT,
	LIBNET_DOWNLOAD_NA,
}DownLoadStatus;

typedef struct _Rsrc_t
{
	char * 			full_url;
	unsigned char *	buffer;
	unsigned int 	buf_len;
	unsigned int 	offset;
	CURL * 			pCurl;
	unsigned char *	yPtr;
	unsigned long 	yBuffEnd;
	unsigned int 	yDataCnt;
	unsigned int 	yPeCacheDataCnt;
	unsigned char * ySwapBuffer;
	unsigned int 	ySwapBuffPtr;
	unsigned int 	ySwapDataLen;
	unsigned int 	yTransTime;
	unsigned int 	idx:				4;
	unsigned int 	reserved:			28;
}Rsrc_t;

typedef struct _RsrcStatus
{
	ID				yPeMutex;
	ID				ySwapMutex;
	DownLoadStatus	yDownLoadStatus;
	unsigned int 	bUsed:				1;
	unsigned int 	NetDownLoadStatus:	1;
	unsigned int	bUsePeCache:		1;
	unsigned int	bPeCacheRequest:	1;
	unsigned int 	reserved:			28;		
}RsrcStatus_t;

#define MAX_RSRC_NUM	16
#define NOR_TO			3
#define PE_TO			(15*60)

static UINT32 url_timeout=15000;
static BOOL NET_DOWNLOAD_STATUS=FALSE;
static unsigned long u2b_play_video_handle = 0;

static RsrcStatus_t tRsrcStatus[MAX_RSRC_NUM] = {{INVALID_ID, INVALID_ID, LIBNET_DOWNLOAD_NA, 0, 0,0,0, 0}};
static BOOL ylibcurl_init = FALSE;

void libnet_abort_url_read(BOOL abort);

int libnet_init(struct libnet_config *pcfg)
{
	libnet_cb = pcfg->cb;
	ylibcurl_init = FALSE;
#ifndef LIB_W5300	
	return network_init(pcfg);
#else
	return 0;
#endif	
}

void libnet_switch_callback(libnet_callback cb)
{
	libnet_cb = cb;
}

BOOL get_net_dl_status()
{

}

void set_net_dl_status(BOOL status)
{

}

static BOOL libnet_get_net_dl_status(int idx)
{
	return tRsrcStatus[idx].NetDownLoadStatus;
}

static void libnet_set_net_dl_status(int idx, BOOL status)
{
	//NET_DOWNLOAD_STATUS=status;
	tRsrcStatus[idx].NetDownLoadStatus = status;
}

static size_t write_data(void *ptr, size_t size, size_t nmemb, void *userdata)
{
	int written = 0;
	libnet_callback notify;
	long long param = 0;
	Rsrc_t * tpRsrc = (Rsrc_t *)userdata;
	unsigned long CopyLen = 0;
	unsigned int index = 0;

	if ((NULL == ptr) || (NULL == tpRsrc))
	{
		return 0;
	}
	index = tpRsrc->idx;
	
	if (LIBNET_DOWNLOAD_ABORT == tRsrcStatus[index].yDownLoadStatus)
	{
		osal_mutex_lock(tRsrcStatus[index].yPeMutex, OSAL_WAIT_FOREVER_TIME);
		tRsrcStatus[index].bPeCacheRequest = 0;
		osal_mutex_unlock(tRsrcStatus[index].yPeMutex);
		written = 0;//CURL_READFUNC_ABORT;
		return written;
	}	

	// If work mode in pe_cache, firstly we sleep here, wait for url_read 
	if ((NULL != tpRsrc) && (1 == tRsrcStatus[index].bUsePeCache))
	{
		libnet_set_net_dl_status(index, TRUE);						// tell libnet_download_to_pecache, can go on	
		while (0 == tRsrcStatus[index].bPeCacheRequest)		// waiting for url_read cmd
		{
			if (LIBNET_DOWNLOAD_ABORT == tRsrcStatus[index].yDownLoadStatus)
			{
				osal_mutex_lock(tRsrcStatus[index].yPeMutex, OSAL_WAIT_FOREVER_TIME);
				tRsrcStatus[index].bPeCacheRequest = 0;
				osal_mutex_unlock(tRsrcStatus[index].yPeMutex);
				written = 0;//CURL_READFUNC_ABORT;
				return written;
			}
			osal_task_sleep(5);
		}
	}

	CopyLen = size * nmemb;
	if (((unsigned long)tpRsrc->yPtr + CopyLen) >= tpRsrc->yBuffEnd)
	{
	//	LIBNET_PRINTF("Buffer Overflow!\n");		
		CopyLen = tpRsrc->yBuffEnd - (unsigned long)tpRsrc->yPtr;

		//step 1: Copy intra data into destination
		MEMCPY(tpRsrc->yPtr, ptr, CopyLen);
		tpRsrc->yPtr += CopyLen;
		if (1 == tRsrcStatus[index].bUsePeCache)
			tpRsrc->yPeCacheDataCnt += CopyLen;//size * nmemb;
		
		// step 2: save extra data into SwapBuffer
		osal_mutex_lock(tRsrcStatus[index].ySwapMutex, OSAL_WAIT_FOREVER_TIME);
		tpRsrc->ySwapDataLen = size * nmemb - CopyLen;		
		MEMCPY(tpRsrc->ySwapBuffer, (unsigned char*)((unsigned long)ptr+CopyLen), tpRsrc->ySwapDataLen);
		tpRsrc->ySwapBuffPtr = 0;
		osal_mutex_unlock(tRsrcStatus[index].ySwapMutex);		
	//	osal_task_sleep(5);
		osal_mutex_lock(tRsrcStatus[index].yPeMutex, OSAL_WAIT_FOREVER_TIME);
		tRsrcStatus[index].bPeCacheRequest = 0;				// set this flag to notice url_read can return
		osal_mutex_unlock(tRsrcStatus[index].yPeMutex);
	}
	else
	{
		MEMCPY(tpRsrc->yPtr, ptr, CopyLen);
		tpRsrc->yPtr += CopyLen;
		if (1 == tRsrcStatus[index].bUsePeCache)
			tpRsrc->yPeCacheDataCnt += CopyLen;//size * nmemb;
	}
	
	while (0 != tpRsrc->ySwapDataLen)			// wait for url_read to consume extra data
	{
		// Wait for SwapData be read out!
		if (LIBNET_DOWNLOAD_ABORT == tRsrcStatus[index].yDownLoadStatus)
		{
			osal_mutex_lock(tRsrcStatus[index].yPeMutex, OSAL_WAIT_FOREVER_TIME);
			tRsrcStatus[index].bPeCacheRequest = 0;
			osal_mutex_unlock(tRsrcStatus[index].yPeMutex);
			written = 0;//CURL_READFUNC_ABORT;
			return written;
		}
		osal_task_sleep(5);
	}
	
	written = size * nmemb;	
		
	notify = libnet_cb;
	if (0 == tpRsrc->yDataCnt)
	{
		LIBNET_PRINTF("param addr:%x\n", &param);
		if (NULL != tpRsrc)
		{
			if (NULL != tpRsrc->pCurl)
				curl_easy_getinfo(tpRsrc->pCurl, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &param);
		}
		LIBNET_PRINTF("Http File size:%llx\n", param);
		if (NULL != notify)
			notify(NET_MSG_DOWNLOAD_START, param);
	}

	tpRsrc->yDataCnt += written;

	if (NULL != notify)
		notify(NET_MSG_DOWNLOAD_PROGRESS, (UINT32)tpRsrc->yDataCnt);
	//LIBNET_PRINTF("Data write to MEM: %d\n", written);
	
	return written;
}

static int libnet_param_free(Rsrc_t *p)
{
	int i = 0;
	
	tRsrcStatus[p->idx].bUsed = 0;
	tRsrcStatus[p->idx].yDownLoadStatus = LIBNET_DOWNLOAD_NA;
	
	if (NULL != p->full_url)
	{
		FREE(p->full_url);
		p->full_url = NULL;
	}

	if (1 == tRsrcStatus[p->idx].bUsePeCache)
	{
		if (NULL != p->ySwapBuffer)
		{
			FREE(p->ySwapBuffer);
			p->ySwapBuffer = NULL;
		}
		if (INVALID_ID != tRsrcStatus[p->idx].yPeMutex)
		{
			osal_mutex_delete(tRsrcStatus[p->idx].yPeMutex);
			tRsrcStatus[p->idx].yPeMutex = INVALID_ID;
		}
		if (INVALID_ID != tRsrcStatus[p->idx].ySwapMutex)
		{
			osal_mutex_delete(tRsrcStatus[p->idx].ySwapMutex);
			tRsrcStatus[p->idx].ySwapMutex = INVALID_ID;
		}
		tRsrcStatus[p->idx].bUsePeCache = 0;
	}
	
	return 0;
}

static int transfer(UINT32 param1, UINT32 param2)
{
	Rsrc_t * tpRsrc;
	CURL *curl;
	libnet_callback notify;
	int msg_code = 0;
	CURLcode ret = CURLE_OK;
	unsigned int iUsePeCache = 0;
	long low_speed_time = 0;

	if (0 == param1)
	{
		LIBNET_PRINTF("Bad Parameter!\n");
		return -1;
	}
	tpRsrc = (Rsrc_t *)param1;
	notify = (libnet_callback)param2;
	low_speed_time = tpRsrc->yTransTime;
	
	iUsePeCache = tRsrcStatus[tpRsrc->idx].bUsePeCache;
	tpRsrc->yPtr = (unsigned char*)((unsigned long)tpRsrc->buffer + tpRsrc->offset);
	if (NULL == tpRsrc->yPtr)
	{
		LIBNET_PRINTF("Invalid Target Address!\n");

		// if PeCache Mode, we do not call libnet_param_free at libcurl -- transfer thread
		// we do this "free" in url_close
		if ((NULL != tpRsrc) && (0 == iUsePeCache))
		{			
			libnet_param_free(tpRsrc);
			FREE(tpRsrc);
			tpRsrc = NULL;
		}
		return -1;
	}
	tpRsrc->yBuffEnd = (unsigned long)tpRsrc->buffer + tpRsrc->buf_len;
	tpRsrc->yDataCnt = 0;

	tRsrcStatus[tpRsrc->idx].yDownLoadStatus = LIBNET_DOWNLOAD_START;
	
	LIBNET_PRINTF("Enter %s()\n", __FUNCTION__);
	curl = curl_easy_init();
	if (NULL == curl)
	{
		LIBNET_PRINTF("curl_easy_init failed!\n");
		if ((NULL != tpRsrc) && (0 == iUsePeCache))
		{			
			libnet_param_free(tpRsrc);
			FREE(tpRsrc);
			tpRsrc = NULL;
		}
		return -1;
	}	
	tpRsrc->pCurl = curl;
	
	curl_easy_setopt(curl, CURLOPT_URL, tpRsrc->full_url);
	curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_data);
	curl_easy_setopt(curl, CURLOPT_FILE, tpRsrc);
	// set Low Speed Limit and Low Speed Timeout to detect NetWork Exception:
	curl_easy_setopt(curl, CURLOPT_LOW_SPEED_LIMIT, 512);
	curl_easy_setopt(curl, CURLOPT_LOW_SPEED_TIME, low_speed_time); // 10 minutes timeout
	// set connection timeout
	curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 1);

	curl_easy_setopt(curl, CURLOPT_FRESH_CONNECT, 1);
	
	LIBNET_PRINTF("Start Data Transfer: url:%s\n", tpRsrc->full_url);
	ret = curl_easy_perform(curl);
	curl_easy_cleanup(curl);
	LIBNET_PRINTF("Data Transfer Completed! ret:%x\n", ret);
	tRsrcStatus[tpRsrc->idx].yDownLoadStatus = LIBNET_DOWNLOAD_FINISH;
	libnet_set_net_dl_status(tpRsrc->idx, FALSE);

	msg_code = tpRsrc->yDataCnt;
	if ((NULL != notify) && CURLE_OPERATION_TIMEDOUT != ret)
		notify(NET_MSG_DOWNLOAD_FINISH, (UINT32)msg_code);

	
	if ((NULL != tpRsrc) && (0 == iUsePeCache))
	{		
		libnet_param_free(tpRsrc);
		FREE(tpRsrc);
		tpRsrc = NULL;
	}	

	return 0;
}

static int create_transfer_task(Rsrc_t *rsrc, libnet_callback cb)
{
	OSAL_T_CTSK t_ctsk;
	OSAL_ID tsk_id;
	t_ctsk.itskpri = OSAL_PRI_NORMAL;
	t_ctsk.stksz = 0x8000;
	t_ctsk.quantum = 10;
	t_ctsk.para1 = (UINT32)rsrc;
	t_ctsk.para2 = (UINT32)cb;
	t_ctsk.name[0] = 'T';
	t_ctsk.name[1] = 'R';
	t_ctsk.name[2] = 'A';
	t_ctsk.task = (FP)transfer;
	tsk_id = osal_task_create(&t_ctsk);
//	LIBNET_PRINTF("tsk_id = %d\n", tsk_id);
	ASSERT(OSAL_INVALID_ID != tsk_id);

	return tsk_id;
}

static Rsrc_t * libnet_param_init(
	char *full_url, 
	unsigned char* buffer,
	unsigned int buf_len,
	unsigned int offset,
	unsigned int use_pecache)
{
	int i = 0;
	Rsrc_t * tpRsrc = NULL;

	for (i = 0; i < MAX_RSRC_NUM; i++)
	{
		if (0 == tRsrcStatus[i].bUsed)
		{
			tRsrcStatus[i].bUsed = 1;
			tRsrcStatus[i].NetDownLoadStatus = FALSE;
			tRsrcStatus[i].yDownLoadStatus = LIBNET_DOWNLOAD_NA;
			break;
		}
	}
	if (MAX_RSRC_NUM == i)
	{
		LIBNET_PRINTF("No more libnet resource!\n");
		return 0;
	}

	tpRsrc = (Rsrc_t *)MALLOC(sizeof(Rsrc_t));
	if (NULL == tpRsrc)
	{
		LIBNET_PRINTF("Memory Malloc Failed!\n");
		return NULL;
	}
	MEMSET(tpRsrc, 0x0, sizeof(Rsrc_t));
		
	tpRsrc->full_url = MALLOC(1024);
	if (NULL == tpRsrc->full_url)
	{
		FREE(tpRsrc);
		tpRsrc = NULL;
		LIBNET_PRINTF("Memory malloc failed!\n");
		return NULL;
	}
	MEMSET(tpRsrc->full_url, 0x0, 1024);
		
//	MEMSET(pRsrc->full_url, 0x0, 1024);
	MEMCPY(tpRsrc->full_url, full_url, STRLEN(full_url));
	tpRsrc->buffer = buffer;
	tpRsrc->buf_len = buf_len;
	tpRsrc->offset = offset;
	tpRsrc->idx = i;
	tpRsrc->yTransTime = NOR_TO;
	tRsrcStatus[i].bUsePeCache = use_pecache;
	if (1 == tRsrcStatus[i].bUsePeCache)
	{
		tpRsrc->yTransTime = PE_TO;//15 * 60;
		tRsrcStatus[i].yPeMutex = osal_mutex_create();
		if (1 == tRsrcStatus[i].bUsePeCache)
		{
			tpRsrc->ySwapBuffer = (unsigned char*)MALLOC(0x20000);
			if (NULL == tpRsrc->ySwapBuffer)
			{
				LIBNET_PRINTF("SwapBuffer malloc failed!\n");
				FREE(tpRsrc->full_url);
				tpRsrc->full_url = NULL;
				FREE(tpRsrc);
				tpRsrc = NULL;
				tRsrcStatus[i].bUsed = 0;
				return NULL;
			}
			MEMSET(tpRsrc->ySwapBuffer, 0x0, 0x20000);
			tpRsrc->ySwapDataLen = 0;
			tpRsrc->ySwapBuffPtr = 0;
			tRsrcStatus[i].ySwapMutex = osal_mutex_create();
			tRsrcStatus[i].bPeCacheRequest = 0;
		}
	}

	return tpRsrc;
}

static int libcurl_init()
{
	if (FALSE == ylibcurl_init)
	{
		if(CURLE_OK != curl_global_init(CURL_GLOBAL_ALL))
		{
			LIBNET_PRINTF("CURL init failed!\n");
			return 0;
		}
		ylibcurl_init = TRUE;
	}

	return 1;
}

/* Full_URL is like "ftp://user:password@192.168.0.1/xxxx", user and password can be NULL*/
unsigned long libnet_download_to_mem(
	char *full_url, 
	unsigned char* buffer, 
	unsigned int buf_len,  
	unsigned int offset)
{
	int i = 0;
	Rsrc_t * tpRsrc = NULL;

	if (0 == libcurl_init())
		return 0;
	
	tpRsrc = libnet_param_init(full_url, buffer, buf_len, offset, 0);
	if (NULL == tpRsrc)
		return 0;
	
	create_transfer_task(tpRsrc, libnet_cb);
	
	return (unsigned long)tpRsrc;
	
}

void libnet_download_abort(unsigned long handle)
{
	Rsrc_t *tpRsrc = (Rsrc_t*)handle;
	int idx = 0;
	
	if(handle == 0)
		return;

	idx = tpRsrc->idx;
	if (LIBNET_DOWNLOAD_START == tRsrcStatus[idx].yDownLoadStatus)
		tRsrcStatus[idx].yDownLoadStatus = LIBNET_DOWNLOAD_ABORT;

/*
	do{
		osal_task_sleep(2);
	}while(LIBNET_DOWNLOAD_ABORT == tRsrcStatus[idx].yDownLoadStatus);//(get_net_dl_status(idx));*/
}

unsigned long libnet_download_to_pecache(
	char *full_url, 
	unsigned char* buffer, 
	unsigned int buf_len,  
	unsigned int offset)
{
	int i = 0;
	Rsrc_t * tpRsrc = NULL;

	if (0 == libcurl_init())
		return 0;

	tpRsrc = libnet_param_init(full_url, buffer, buf_len, offset, 1);
	if (NULL == tpRsrc)
		return 0;
	
	create_transfer_task(tpRsrc, libnet_cb);
	
	return (unsigned long)tpRsrc;
}

FILE *url_open(const char *path, unsigned char* buffer, unsigned int buf_len, unsigned int offset)
{
	FILE *tmp = NULL;
	UINT32 time_tick;
	BOOL bret =FALSE;
	char * url = NULL;
	Rsrc_t *tpRsrc = NULL;
	// TODO: 
	// step 1: build a net Transfer-Thread
	url = (char *)path;
	u2b_play_video_handle = libnet_download_to_pecache(url, buffer, buf_len, offset);
	if (0 != u2b_play_video_handle)
	{
		LIBNET_PRINTF("u2b: %x\n", u2b_play_video_handle);
		tpRsrc = (Rsrc_t *)u2b_play_video_handle;
	}
	else
		return NULL;
	
	// step 2: wait the Transfer-Thread building status, if build OK, goto step3
	//			else, wait here
	time_tick = osal_get_tick();
	while(osal_get_tick()-time_tick < url_timeout)
	{
		if(libnet_get_net_dl_status(tpRsrc->idx)) 
		{
			//url connet is ok
			bret= TRUE;
			break;
		}
	//	if(abort_url_read)
	//	{
	//		abort_url_read=FALSE;
	//		break;
	//	}
		osal_task_sleep(2);
	}	

	// step 3: Malloc a FILE pointer, return this pointer
	if(bret)
	{	
		tmp=(FILE*)MALLOC(sizeof(FILE));
		if(tmp)
		{
			MEMSET(tmp, 0, sizeof(FILE));
			LIBNET_PRINTF("url_open OK !!!!\n");
		}
	}
	else
	{
		libnet_param_free(tpRsrc);
		tpRsrc = NULL;
		LIBNET_PRINTF("url_open time out %d ms!!!!\n",url_timeout);
	}
    
    return tmp;
}

size_t url_read(void *ptr, size_t size, size_t nmemb, FILE *stream)
{
	Rsrc_t * tpRsrc;
	unsigned int ReadLen = 0;
	unsigned int index = 0;
	// TODO: pe_cache need to fill data, so call this function
	if (NULL == ptr)
	{
		LIBNET_PRINTF("%s Invalid Input Parameter!\n", __FUNCTION__);
		return 0;
	}
	
	if (0 == u2b_play_video_handle)
	{
		LIBNET_PRINTF("Bad u2b play handle!\n");
		return 0;
	}
	tpRsrc = (Rsrc_t *)u2b_play_video_handle;
	index = tpRsrc->idx;
	if (0 == tRsrcStatus[index].bUsed)
	{
		return 0;
	}

	if (0 < tpRsrc->ySwapDataLen)
	{
		if (tpRsrc->ySwapDataLen > size * nmemb)
			ReadLen = size * nmemb;
		else
			ReadLen = tpRsrc->ySwapDataLen;

		MEMCPY(ptr, (tpRsrc->ySwapBuffer + tpRsrc->ySwapBuffPtr), ReadLen);

		osal_mutex_lock(tRsrcStatus[index].ySwapMutex, OSAL_WAIT_FOREVER_TIME);
		tpRsrc->ySwapDataLen -= ReadLen;
		osal_mutex_unlock(tRsrcStatus[index].ySwapMutex);
		
		tpRsrc->ySwapBuffPtr += ReadLen;
		if (0 == tpRsrc->ySwapDataLen)
			tpRsrc->ySwapBuffPtr = 0;

		//tpRsrc->yDataCnt += ReadLen;
		return ReadLen;
	}
	
	osal_mutex_lock(tRsrcStatus[index].yPeMutex, OSAL_WAIT_FOREVER_TIME);
	tRsrcStatus[index].bPeCacheRequest = 1;
	osal_mutex_unlock(tRsrcStatus[index].yPeMutex);
	
	tpRsrc->yPtr = ptr;
	tpRsrc->yBuffEnd = (unsigned long)ptr + size * nmemb;	
	tpRsrc->yPeCacheDataCnt = 0;

	LIBNET_PRINTF("Begin url_read! Start: %x Size:%x\n", tpRsrc->yPtr, size*nmemb);
	while (1 == tRsrcStatus[index].bPeCacheRequest)
	{
		LIBNET_PRINTF("url_read sleep!\n");
		osal_task_sleep(2);						// wait for write_data finished!
		if (LIBNET_DOWNLOAD_START != tRsrcStatus[index].yDownLoadStatus)
			break;
	}

	LIBNET_PRINTF("Finish url_read! %x\n", tpRsrc->yPeCacheDataCnt);
	return tpRsrc->yPeCacheDataCnt;
}

size_t url_close(FILE * fp)
{
	Rsrc_t * tpRsrc;
	// TODO: file decode finished, or abort; need to FREE the FILE pointer
	LIBNET_PRINTF("===url_close===!\n");

	libnet_abort_url_read(TRUE);

	if (0 != u2b_play_video_handle)
	{
		tpRsrc = (Rsrc_t *)u2b_play_video_handle;
		do{
			osal_task_sleep(2);
		}while(LIBNET_DOWNLOAD_ABORT == tRsrcStatus[tpRsrc->idx].yDownLoadStatus);
		libnet_param_free(tpRsrc);
		tpRsrc = NULL;
		u2b_play_video_handle = 0;
	}
	
	if(fp!=NULL)
	{
		FREE(fp);
		fp = NULL;
	}

	return 0;
}

void libnet_set_wait_flag(BOOL set)
{

}

void libnet_abort_url_read(BOOL abort)
{
	if (abort)
	{
		if (0 != u2b_play_video_handle)
			libnet_download_abort(u2b_play_video_handle);

	}
}


